﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Drawing.Drawing2D;
using System.Text;
using System.Windows.Forms;
using Funkcje;

namespace Wykres3D
{
    public partial class KontrolkaWykresu3D : UserControl
    {
        #region Pola klasy
        private bool czyWykresJestPrzesuwany = false;
        private int pozycjaMyszyX;
        private int pozycjaMyszyY;

        private int obrotX;
        private int obrotY;
        private int obrotZ;
        private int obrotX3D;
        private int obrotY3D;
        private int obrotZ3D;
        private int obrotX2D = 0;
        private int obrotY2D = 0;
        private int obrotZ2D = -360;
        private int przesuniecieX;
        private int przesuniecieY;
        private int powiekszenie;
        private bool widokPerspektywy = true;

        private List<Point3D> listaPunktow;
        private float promienPunktuNaWykresie;
        private float gestoscSiatkiWykresu;

        private Surface3DRenderer sr;
        private float gestoscPowierzchniWykresu;
        private bool rysujKonturyNaPowierzchni;
        private string _funkcja;
        private PointF _poczatekDziedziny;
        private PointF punktKoncowyDziedziny;
        #endregion

        #region Konstruktory
        public KontrolkaWykresu3D(string funkcjaDoNarysowania, PointF punktPoczatkowyDziedziny, PointF punktKoncowyDziedziny, float gestoscPowierzchniWykresu, bool rysujKonturPowierzchni, Color kolorKonturuPowierzchni, ColorSchema kolorPowierzchni)
        {
            //TWORZENIE WSTEPNEJ KONFIGURACJA WYKRESU
            this.listaPunktow = new List<Point3D>();
            this.gestoscSiatkiWykresu = 0.1f;
            this.gestoscPowierzchniWykresu = 0.5f;
            this._poczatekDziedziny = punktPoczatkowyDziedziny;
            this.punktKoncowyDziedziny = punktKoncowyDziedziny;
            this.promienPunktuNaWykresie = (Math.Abs(punktKoncowyDziedziny.X - punktPoczatkowyDziedziny.X) * gestoscSiatkiWykresu) * 2.0f;
            this._funkcja = funkcjaDoNarysowania;
            this.rysujKonturyNaPowierzchni = rysujKonturPowierzchni;
            this.obrotX = -174;
            this.obrotY = -193;
            this.obrotZ = -144;
            this.obrotX3D = obrotX;
            this.obrotY3D = obrotY;
            this.obrotZ3D = obrotZ;
            this.przesuniecieX = 0;
            this.przesuniecieY = 0;
            this.powiekszenie = 200;

            //INICJALIZACJA KOMPONENTOW
            InitializeComponent();

            //KONFIGURACJA WYKRESU
            sr = new Surface3DRenderer(powiekszenie, obrotX, obrotY, obrotZ, przesuniecieX, przesuniecieY, ClientRectangle.Width, ClientRectangle.Height, 0.5, 0, 0);
            sr.DrawContour = rysujKonturyNaPowierzchni;
            sr.Density = gestoscPowierzchniWykresu;
            sr.SetFunction(funkcjaDoNarysowania);
            sr.StartPoint = punktPoczatkowyDziedziny;
            sr.EndPoint = punktKoncowyDziedziny;
            sr.ColorSchema = kolorPowierzchni;
            sr.PenColor = kolorKonturuPowierzchni;
            sr.PenWidth = 0.1f;

            //KONFIGURACJA KONTROLKI
            ControlPlot3D_Resize(null, null);
            ResizeRedraw = true;
            DoubleBuffered = true;

            //REJESTRACJA OBSLUGI ZDARZEN
            MouseDown += mouseLeftButtonDown;
            MouseUp += mouseLeftButtonUp;
            MouseMove += mouseMovePlot;
            MouseWheel += mouseWheelScroll;
            trackBarObrotX.MouseWheel += mouseWheelScroll;
            trackBarObrotY.MouseWheel += mouseWheelScroll;
            trackBarObrotZ.MouseWheel += mouseWheelScroll;
            trackBarObrotX.Value = obrotX;
            trackBarObrotY.Value = obrotY;
            trackBarObrotZ.Value = obrotZ;
        }

        public KontrolkaWykresu3D()
        {
            //TWORZENIE WSTEPNEJ KONFIGURACJA WYKRESU
            this.listaPunktow = new List<Point3D>();
            this.gestoscSiatkiWykresu = 0.5f;
            this.gestoscPowierzchniWykresu = 0.5f;
            this._poczatekDziedziny = new PointF(-8, -8);
            this.punktKoncowyDziedziny = new PointF(8, 8);
            this.promienPunktuNaWykresie = (Math.Abs(punktKoncowyDziedziny.X - _poczatekDziedziny.X) * gestoscSiatkiWykresu/2.0f);
            this._funkcja = "abs(x1) + sin(x2)";
            this.rysujKonturyNaPowierzchni = true;

            this.obrotX = -174;
            this.obrotY = -193;
            this.obrotZ = -144;
            this.obrotX3D = obrotX;
            this.obrotY3D = obrotY;
            this.obrotZ3D = obrotZ;
            this.przesuniecieX = 0;
            this.przesuniecieY = 0;
            this.powiekszenie = 200;

            //INICJALIZACJA KOMPONENTOW
            InitializeComponent();

            //KONFIGURACJA WYKRESU
            sr = new Surface3DRenderer(powiekszenie, obrotX, obrotY, obrotZ, przesuniecieX, przesuniecieY, ClientRectangle.Width, ClientRectangle.Height, 0.5, 0, 0);
            sr.DrawContour = rysujKonturyNaPowierzchni;
            sr.Density = gestoscPowierzchniWykresu;
            sr.SetFunction(_funkcja);
            sr.StartPoint = _poczatekDziedziny;
            sr.EndPoint = punktKoncowyDziedziny;
            sr.ColorSchema = new ColorSchema(150);
            sr.PenColor = Color.FromArgb(50, Color.Black);
            sr.PenWidth = 0.1f;
            
            //KONFIGURACJA KONTROLKI
            ControlPlot3D_Resize(null, null);
            ResizeRedraw = true;
            DoubleBuffered = true;

            //REJESTRACJA OBSLUGI ZDARZEN
            MouseDown += mouseLeftButtonDown;
            MouseUp += mouseLeftButtonUp;
            MouseMove += mouseMovePlot;
            MouseWheel += mouseWheelScroll;
            trackBarObrotX.MouseWheel += mouseWheelScroll;
            trackBarObrotY.MouseWheel += mouseWheelScroll;
            trackBarObrotZ.MouseWheel += mouseWheelScroll;
            trackBarObrotX.Value = obrotX;
            trackBarObrotY.Value = obrotY;
            trackBarObrotZ.Value = obrotZ;
        }

        #endregion

        #region Wlasciwosci

        public List<Point3D> ListaPunktow
        {
            get { return listaPunktow; }
            set { listaPunktow = value; }
        }

        public float GestoscSiatkiWykresu
        {
            get { return gestoscSiatkiWykresu; }
            set { gestoscSiatkiWykresu = value; }
        }

        public float GestoscPowierzchniWykresu
        {
            get { return gestoscPowierzchniWykresu; }
            set
            {
                gestoscPowierzchniWykresu = value;
                sr.Density = gestoscPowierzchniWykresu;
            }
        }

        public bool RysujKonturyNaPowierzchni
        {
            get { return rysujKonturyNaPowierzchni; }
            set
            {
                rysujKonturyNaPowierzchni = value;
                sr.DrawContour = rysujKonturyNaPowierzchni;
            }
        }

        public string Funkcja
        {
            get { return _funkcja; }
            set { 
                _funkcja = value;
                sr.SetFunction(_funkcja);
            }
        }

        public PointF PoczatekDziedziny
        {
            get { return _poczatekDziedziny; }
            set
            {
                _poczatekDziedziny = value;
                sr.StartPoint = _poczatekDziedziny;
            }
        }

        public PointF KoniecDziedziny
        {
            get { return punktKoncowyDziedziny; }
            set
            {
                punktKoncowyDziedziny = value;
                sr.EndPoint = punktKoncowyDziedziny;
            }
        }

        #endregion

        #region Rysowanie elementow dodatkowych
        private void rysujPunktyNaWykresu(Graphics graphics)
        {
            Brush brush = new SolidBrush(Color.Red);
            graphics.SmoothingMode = SmoothingMode.AntiAlias;
            foreach (var punkt in listaPunktow)
            {
                PointF pointToDraw = sr.Project(punkt.x, punkt.y, punkt.z);
                graphics.FillEllipse(brush, pointToDraw.X, pointToDraw.Y, promienPunktuNaWykresie, promienPunktuNaWykresie);
            }
        }

        private void rysujSiatkeWykresu(Graphics graphics)
        {
            Pen gridPen = new Pen(Color.FromArgb(30, Color.Black), 0.1f);
            Pen axisPen = new Pen(Color.Black, 0.5f);

            PointF startPoint = sr.StartPoint;
            PointF endPoint = sr.EndPoint;
            double step = Math.Abs(endPoint.X - startPoint.X) * gestoscSiatkiWykresu;
            double stepY = Math.Abs(sr.MaxFunctionValue - sr.MinFunctionValue) * gestoscSiatkiWykresu * 2;

            graphics.SmoothingMode = SmoothingMode.AntiAlias;

            //rysowanie osi
            PointF poczatekOsi = sr.Project(startPoint.X, startPoint.Y, 0);
            PointF koniecOsiX1 = sr.Project(endPoint.X + step, startPoint.Y, 0);
            PointF koniecOsiX2 = sr.Project(startPoint.X, endPoint.Y + step, 0);

            PointF gornyKoniecOsiY = sr.Project(startPoint.X, startPoint.Y, sr.MaxFunctionValue);
            PointF dolnyKoniecOsiY = sr.Project(startPoint.X, startPoint.Y, sr.MinFunctionValue);

            PointF etykietaX1 = sr.Project(startPoint.X + step, startPoint.Y - step, 0);
            PointF etykietaX2 = sr.Project(startPoint.X - step, startPoint.Y + step, 0);
            PointF etykietaY = sr.Project(startPoint.X - step, startPoint.Y, sr.MaxFunctionValue+sr.MaxFunctionValue*0.1f);

            for (double value = sr.MinFunctionValue; value < sr.MaxFunctionValue + stepY; value += stepY)
            {
                PointF poczatekLinii = sr.Project(startPoint.X + step / 4, startPoint.Y - step / 4, value);
                PointF koniecLiniiPoziomej = sr.Project(startPoint.X - step / 4, startPoint.Y + step / 4, value);
                PointF wartoscOsiY = sr.Project(startPoint.X - step / 4, startPoint.Y + step / 2, value + value * 0.1);
                graphics.DrawLine(gridPen, poczatekLinii, koniecLiniiPoziomej);
                graphics.DrawString(Math.Round(value, 2).ToString(), new Font(FontFamily.GenericSansSerif, 10f, FontStyle.Bold), new SolidBrush(Color.Black), wartoscOsiY);
            }

            graphics.DrawLine(axisPen, poczatekOsi, koniecOsiX1);
            graphics.DrawLine(axisPen, poczatekOsi, koniecOsiX2);
            graphics.DrawLine(axisPen, poczatekOsi, gornyKoniecOsiY);
            graphics.DrawLine(axisPen, poczatekOsi, dolnyKoniecOsiY);
            graphics.DrawString("X1", new Font(FontFamily.GenericSansSerif, 12f, FontStyle.Bold), new SolidBrush(Color.Black), etykietaX1);
            graphics.DrawString("X2", new Font(FontFamily.GenericSansSerif, 12f, FontStyle.Bold), new SolidBrush(Color.Black), etykietaX2);
            graphics.DrawString("Y", new Font(FontFamily.GenericSansSerif, 12f, FontStyle.Bold), new SolidBrush(Color.Black), etykietaY);

            //rysowanie siatki
            for (double x1 = startPoint.X; x1 < endPoint.X + step; x1 += step)
            {
                for (double x2 = startPoint.Y; x2 < endPoint.Y + step; x2 += step)
                {
                    PointF poczatekLinii = sr.Project(x1, x2, 0);
                    PointF koniecLiniiPoziomej = sr.Project(endPoint.X + step, x2, 0);
                    PointF koniecLiniiPionowej = sr.Project(x1, endPoint.Y + step, 0);
                    graphics.DrawLine(gridPen, poczatekLinii, koniecLiniiPoziomej);
                    graphics.DrawLine(gridPen, poczatekLinii, koniecLiniiPionowej);
                    graphics.DrawString(Math.Round(x1, 2).ToString(), new Font(FontFamily.GenericSansSerif, 10f, FontStyle.Regular), new SolidBrush(Color.Black), koniecLiniiPionowej);
                    graphics.DrawString(Math.Round(x2, 2).ToString(), new Font(FontFamily.GenericSansSerif, 10f, FontStyle.Regular), new SolidBrush(Color.Black), koniecLiniiPoziomej);
                }

            }

        }

        #endregion

        #region Obsluga myszy
        private void mouseWheelScroll(object sender, MouseEventArgs e)
        {
            var wartoscKolkaMyszy = e.Delta / 12;
            powiekszenie = (int)(sr.Dpi + wartoscKolkaMyszy);
            if (powiekszenie < 1)
            {
                powiekszenie = 1;
            }
            odswiezWykres();
        }

        private void mouseLeftButtonDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                czyWykresJestPrzesuwany = true;
                this.Cursor = Cursors.SizeAll;
                pozycjaMyszyX = e.X;
                pozycjaMyszyY = e.Y;
            }
        }

        private void mouseLeftButtonUp(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left && czyWykresJestPrzesuwany)
            {
                czyWykresJestPrzesuwany = false;
            }
            this.Cursor = Cursors.Default;
        }

        private void mouseMovePlot(object sender, MouseEventArgs e)
        {
            if (czyWykresJestPrzesuwany)
            {
                przesuniecieX += (e.X - pozycjaMyszyX) / 10;
                przesuniecieY += (e.Y - pozycjaMyszyY) / 10;
                odswiezWykres();
            }
        }
        #endregion

        #region Obsluga suwakow
        private void trackBarObrotX_Scroll(object sender, EventArgs e)
        {
            buttonWidok3d.Focus();
            obrotX3D = trackBarObrotX.Value;
            obrotX = obrotX3D;
            //System.Console.WriteLine("X=" + obrotX + " , Y="+obrotY + " , Z="+obrotZ);
            odswiezWykres();
        }

        private void trackBarObrotY_Scroll(object sender, EventArgs e)
        {
            buttonWidok3d.Focus();
            obrotY3D = trackBarObrotY.Value;
            obrotY = obrotY3D;
            //System.Console.WriteLine("X=" + obrotX + " , Y=" + obrotY + " , Z=" + obrotZ);
            odswiezWykres();
        }

        private void trackBarObrotZ_Scroll(object sender, EventArgs e)
        {
            buttonWidok3d.Focus();
            obrotZ3D = trackBarObrotZ.Value;
            obrotZ = obrotZ3D;
            //System.Console.WriteLine("X=" + obrotX + " , Y=" + obrotY + " , Z=" + obrotZ);
            odswiezWykres();
        }

        #endregion

        #region Obsluga panelu kontrolki
        private void ControlPlot3D_Resize(object sender, EventArgs e)
        {
            odswiezWykres();
        }

        private void ControlPlot3D_Paint(object sender, PaintEventArgs e)
        {
            e.Graphics.Clear(BackColor);
            sr.RenderSurface(e.Graphics);
            rysujSiatkeWykresu(e.Graphics);
            rysujPunktyNaWykresu(e.Graphics);
        }

        #endregion

        #region Metody pomocnicze
        private void odswiezWykres()
        {
            sr.ReCalculateTransformationsCoeficients(powiekszenie, obrotX, obrotY, obrotZ, przesuniecieX, przesuniecieY, ClientRectangle.Width, ClientRectangle.Height, 0.5, 0, 0);
            Invalidate();
        }

        #endregion

        private void buttonPowieksz_Click(object sender, EventArgs e)
        {
            powiekszenie = (int)(sr.Dpi + 20);
            if (powiekszenie < 1)
            {
                powiekszenie = 1;
            }
            odswiezWykres();
        }

        private void buttonPomniejsz(object sender, EventArgs e)
        {
            powiekszenie = (int)(sr.Dpi - 20);
            if (powiekszenie < 1)
            {
                powiekszenie = 1;
            }
            odswiezWykres();
        }

        private void buttonWidok3d_Click(object sender, EventArgs e)
        {
            if(widokPerspektywy)
            {
                //bylo 3D
                groupBoxWidok.Visible = false;
                buttonWidok3d.Text = "3D";
                widokPerspektywy = false;
                obrotX = obrotX2D;
                obrotY = obrotY2D;
                obrotZ = obrotZ2D;
                odswiezWykres();
            }else
            {
                //bylo 2D
                groupBoxWidok.Visible = true;
                buttonWidok3d.Text = "2D";
                widokPerspektywy = true;
                obrotX = obrotX3D;
                obrotY = obrotY3D;
                obrotZ = obrotZ3D;
                trackBarObrotX.Value = obrotX;
                trackBarObrotY.Value = obrotY;
                trackBarObrotZ.Value = obrotZ;
                odswiezWykres();
            }
        }

        private void trackBarGestoscPowierzchni_Scroll(object sender, EventArgs e)
        {
            buttonWidok3d.Focus();
            gestoscPowierzchniWykresu = trackBarGestoscPowierzchni.Value / 100.0f;
            sr.Density = gestoscPowierzchniWykresu;
            odswiezWykres();
        }

        private void checkBoxRysujKontury_CheckedChanged(object sender, EventArgs e)
        {
            rysujKonturyNaPowierzchni = checkBoxRysujKontury.Checked;
            sr.DrawContour = rysujKonturyNaPowierzchni;
            odswiezWykres();
        }
    }
}
